using System;
using ValidationFramework.Configuration;
using ValidationFramework.Reflection;
using ValidationFramework.Configuration.Xml;

namespace ValidationFramework
{
    /// <summary>
    /// Responsible for Configuring, executing, and reporting the results of a test against a specification.
    /// </summary>
#if (!SILVERLIGHT)
    [Serializable]
#endif
    public class Rule
    {
        // --- Fields
        private string _ErrorMessage;


        // --- Constructors
        public Rule(ISpecification specification)
        {
            RuleSet = string.Empty;
            ErrorMessage = string.Empty;
            UseErrorMessageProvider = false;
            Specification = specification;
        }


        // --- Properties

        /// <summary>
        /// The validator to test the value of this member against
        /// </summary>
        public ISpecification Specification { get; set; }

        // --- IRule | Properties

        public string RuleSet { get; set; }

        /// <summary>
        /// Gets the error message for this <see cref="Rule"/>.
        /// </summary>
        /// <remarks>
        /// In the case of an application with a user interface this property would often be displayed to the user. So it should contain proper grammar and punctuation.
        /// </remarks>
        public string ErrorMessage
        {
            get
            {
                return _ErrorMessage;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentException("Error message cannot be null", "errorMessage");
                }
                _ErrorMessage = value;
            }
        }

        /// <summary>
        /// Get a <see cref="bool"/> indicating if <see cref="ConfigurationService.ErrorMessageProvider"/> should be used when determining the error message for this <see cref="Rule"/>.
        /// </summary>
        public bool UseErrorMessageProvider
        {
            get;
            set;
        }


        // --- IRule | Methods

        /// <summary>
        /// Tests an object against this rule's validators and returns a property built result.
        /// </summary>
        /// <remarks>
        /// If <see cref="UseErrorMessageProvider"/> is <c>true</c> then <see cref="ConfigurationService.ErrorMessageProvider"/> will be called to get the error message. Otherwise the <see cref="ErrorMessage"/> will be used.
        /// </remarks>
        /// <param name="targetToTest">The value of the object containing the member to validate.</param>
        /// <param name="targetMemberValue">The value of the member to validate.</param>
        /// <param name="validationContext">An <see cref="object"/> that contains data for the <see cref="Rule"/> to validate.</param>
        /// <param name="infoDescriptor">The <see cref="InfoDescriptor"/> for the <see cref="ValidationResult"/> being generated.</param>
        /// <returns>A constructed <see cref="ValidationResult"/>.</returns>
        public ValidationResult Validate(ValidationContext validationContext)
        {
            // create a result, by default the result is for a passed test.
            var result = new ValidationResult(this, validationContext.Target);

            // Test. Mark the result on failure.
            if (!Specification.IsSatisfiedBy(validationContext.Target))
            {
                string policyType;
                if (validationContext.MemberReflectionInfo is CachedPropertyInfo)
                    policyType = "property";
                else
                    policyType = "parameter";

                var message = ResolveErrorMessage(validationContext.MemberName, policyType);
                result.MarkAsFailed(message);
            }

            return result;
        }

        /// <summary>
        /// Checks if a rule can be applied to the supplied type
        /// </summary>
        /// <param name="targetType">The type who's applicability will be checked.</param>
        public bool CanBeAppliedToType(Type targetType)
        {
            return Specification.CanBeSatisfiedBy(targetType);

            //if (!this.Validator.CheckType(targetType))
            //{
            //    const string invalidTypeFormat = "This rule cannot be applied to the following type: '{0}'. The rule's '{1}' CANNOT be used to test a '{0}'";
            //    var friendlyValidatorName = this.Validator.GetType().ToUserFriendlyString();
            //    var friendlyTargetTypeName = targetType.ToUserFriendlyString();
            //    var exceptionMessage = string.Format(invalidTypeFormat, friendlyTargetTypeName, friendlyValidatorName);
            //    throw new ArgumentException(exceptionMessage);
            //}
        }

        /// <summary>
        /// Checks if the current <see cref="Rule"/> is equivalent to another <see cref="Rule"/>.
        /// </summary>
        /// <remarks>
        /// Is often used when a new <see cref="Rule"/> is added to a <see cref="RuleCollection"/> to ensure it doesn't end up containing duplicate rules.
        /// </remarks>
        /// <param name="rule">The <see cref="Rule"/> to check for equivalence.</param>
        /// <returns><see langword="true"/> if <paramref name="rule"/> is equivalent to the current <see cref="Rule"/>; otherwise <see langword="false"/>.</returns>
        public bool IsEquivalent(Rule rule)
        {
			if (rule == null)
				return false;

			if (this.RuleSet != rule.RuleSet)
				return false;

			var validatorToCompare = rule.Specification;

			if (validatorToCompare.GetType() == Specification.GetType())
				return this.Specification.IsEquivalentTo(validatorToCompare);

			return false;
        }

        // Methods

        /// <summary>
        /// Resolves the error message for the failed tests using the test's configuration.
        /// </summary>
        /// <returns></returns>
        public string ResolveErrorMessage(string memberName, string descriptorType)
        {
            // Try the message provided at configuration time
            if (!string.IsNullOrEmpty(ErrorMessage))
                return ErrorMessage;

            // Try ErrorMessageProvider
            if (UseErrorMessageProvider)
            {
				if (Validation.ErrorMessageProvider == null)
                {
                    throw new InvalidOperationException("useErrorMessageProvider is true but no ErrorMessageProvider is specified in the ConfigurationService.");
                }
                // check the provider for a default validator message
				var providedMessage = Validation.ErrorMessageProvider.RetrieveErrorMessage(this, memberName, descriptorType);
                return providedMessage;
            }

            // Try the validator's default message
            var defaultMessage = Specification.DefaultMessage(memberName, "member");
            if (!string.IsNullOrEmpty(defaultMessage))
            {
                return defaultMessage;
            }

            // Failover to a standard default
            return string.Format("{0} is invalid", memberName);
        }
    }
}